/*
 * Copyright (C) 2010-2011 Graz University of Technology, Austria. All Rights reserved. 
 *
 * Contact: Simone Kopeinik <simone.kopeinik@tugraz.at>
 * 	   Graz University of Technology, Knowledge Management Institute, Brückenkopfgasse 1/6, 8020 Graz 	
 * 	   <http://www.kmi.tugraz.at/>
 * 
 * This software is part of the TARGET platform developed by the
 * TARGET EC-funded project www.reachyourtarget.org
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The GNU General Public License is available at <http://www.gnu.org/licenses/>.
 *
 */
package at.tugraz.kmi.css.commons;

import java.util.Arrays;

public final class Range<T extends Comparable<T>> implements
		Comparable<Range<T>> {

	private final T	start;
	private final T	end;

	public Range(T start, T end) {
		this.start = start;
		this.end = end;
	}

	/**
	 * Calculates if the range includes a certain object.
	 * 
	 * @param arg
	 *            the object that is probably included .
	 * @return true if the range includes the object.
	 */
	public boolean includes(T arg) {
		return !(arg.compareTo(start) < 1) && !(arg.compareTo(end) > 1);
	}

	/**
	 * Calculates if one range includes another range.
	 * 
	 * @param arg
	 *            the range to compare.
	 * @return true if one range includes another range.
	 */
	public boolean includes(Range<T> arg) {
		return this.includes(arg.start) && this.includes(arg.end);
	}

	/**
	 * Calculates if another range overlaps this range
	 * 
	 * @param arg
	 *            the range to check.
	 * @return true if there is an overlap.
	 */
	public boolean overlaps(Range<T> arg) {
		return arg.includes(start) || arg.includes(end) || this.includes(arg);
	}

	/**
	 * Caculates the gap between this and another range.
	 * 
	 * @param arg
	 *            the range to compare.
	 * @return the gap range.
	 */
	public Range<T> gap(Range<T> arg) {
		if (this.overlaps(arg)) {
			return null;
		}
		Range<T> lower, higher;
		if (this.compareTo(arg) < 0) {
			lower = this;
			higher = arg;
		} else {
			lower = arg;
			higher = this;
		}
		return new Range<T>(lower.end, higher.start);
	}

	/**
	 * 
	 * Calculates if range isempty.
	 * 
	 * @return true if range is empty.
	 */
	public boolean isEmpty() {
		return start.compareTo(end) > 1;
	}

	public boolean abuts(Range<T> arg) {
		return !this.overlaps(arg) && this.gap(arg).isEmpty();
	}

	/**
	 * 
	 * @param args
	 *            a array of ranges.
	 * @return true if the ranges are continuous.
	 */
	@SuppressWarnings("unchecked")
	public static boolean isContiguous(@SuppressWarnings("rawtypes") Range[] args) {
		Arrays.sort(args);
		for (int i = 0; i < args.length - 1; i++) {
			if (!args[i].abuts(args[i + 1])) {
				return false;
			}
		}
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int compareTo(Range<T> other) {
		if (!start.equals(other.start)) {
			return start.compareTo(other.start);
		}
		return end.compareTo(other.end);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((end == null) ? 0 : end.hashCode());
		result = prime * result + ((start == null) ? 0 : start.hashCode());
		return result;
	}

	/**
	 * {@inheritDoc}
	 */ 
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Range other = (Range) obj;
		if (end == null) {
			if (other.end != null) {
				return false;
			}
		} else if (!end.equals(other.end)) {
			return false;
		}
		if (start == null) {
			if (other.start != null) {
				return false;
			}
		} else if (!start.equals(other.start)) {
			return false;
		}
		return true;
	}

}
